#pragma once

#define _CRT_RAND_S

#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <stdlib.h>
#include <limits.h>
#include <fstream>
#include <time.h>
#include <string>

#include "Ddavidlib_clustering_basic_definitions.h"
#include "metis.h"
//#include "libhmetis.h"

using namespace std;

namespace Ddavidlib_clustering
{
  // needed information for consensus clustering algorithms
  class ConsensusClusteringInformation
  {
  public:
    string used_consensus_clustering_approach;
    string original_clustering_direction;  // must end by '\'
    string input_direction;  // must end by '\'
    string output_direction;  // must end by '\'
    string output_filename;  // do not need ".txt", it will be automatically added by WriteClusteringResultToFile()
    unsigned int point_number;
    unsigned int incremental_point_number;
    string cluster_number_choosing_method;
    double cluster_number_multiple;
    unsigned int cluster_number;  // needed for some algorithms
    vector<string> clustering_filename_list;  // .ini for DataReader
    vector<string> true_label_clustering_filename_list;  // .ini for DataReader
    // 0.0 <= error_threshold <= 2.0
    double error_threshold;  // needed for LFCARS using Loose Fragments
    unsigned int core_fragment_size_threshold;  // needed for LFCARS using Loose Fragments
    unsigned int execution_times;

    bool output_loose_fragment_clustering_result;
    string output_loose_fragment_clustering_result_filename;

    bool given_initialization;
    string used_initialization_approach;
    string given_initialization_filename;

    bool using_missing_mask_file;
    string missing_mask_filename;
    double missing_rate;

    bool automatic_ANMI_evaluation;
    bool automatic_Rand_evaluation;
    bool automatic_VI_evaluation;
    string ANMI_evaluation_output_filename;
    string Rand_evaluation_output_filename;
    string VI_evaluation_output_filename;
    string evaluation_information_output_filename;

    bool automatic_true_label_evaluation;
    string true_label_ANMI_evaluation_output_filename;
    string true_label_Rand_evaluation_output_filename;
    string true_label_VI_evaluation_output_filename;
    string true_label_evaluation_information_output_filename;

    bool LFCARSv100_use_avg_similarity;
    string loose_fragment_sampling_method;
    string output_loose_fragment_sampling_filename;
    vector<string> incremental_clustering_filename_list;  // .ini for DataReader

    bool debug_show_round;
    string debug_show_round_output_filename;
    bool debug_show_fragment_number;
    string debug_show_fragment_number_output_filename;
    bool debug_show_execution_time;
    string debug_show_execution_time_output_filename;
    bool debug_show_original_cluster_number;
    string debug_show_original_cluster_number_output_filename;

  public:
    ConsensusClusteringInformation();
    ~ConsensusClusteringInformation();
  };

  class Timer
  {
  private:
    map<string, double> time_counter;
    clock_t last_time;

  public:
    Timer();
    ~Timer();

    void Reset(void);
    void PushButton(void); // Push at the start time
    void PushButton(string event_name); // Push at the end time

    // OutputToFile returns the total time
    double OutputToFile(string filename);
    double OutputToFile(ofstream &file);
  };

  class ConsensusClusteringApproach
  {
  public:
    string approach_name;
    unsigned int point_number;
    unsigned int cluster_number;
    unsigned int original_clustering_number;
    vector<int> clustering_result;
    set<unsigned int> cluster_id_list;
    unsigned int max_cluster_number;
    unsigned int sum_cluster_number;
    bool multi_execution_supporting;

    Timer timer;

    bool debug_show_original_cluster_number;
    string debug_show_original_cluster_number_output_filename;

  public:
    ConsensusClusteringApproach(const ConsensusClusteringInformation &consensus_clustering_information);
    ~ConsensusClusteringApproach(void);

    // AddOriginalClustering MUST update original_clustering_number, sum_cluster_number and max_cluster_number
    virtual void AddOriginalClustering(const vector<int> &input_clustering) = 0;
    virtual void Initialize(void) = 0;
    virtual void Run(void) = 0;
    // Reset MUST clear original_clustering_number, sum_cluster_number and max_cluster_number
    virtual void Reset(void) = 0;

    void GivenKRandomClusteringInitialize(vector<int> &initialized_clustering, int input_cluster_number);
    void MaxKRandomClusteringInitialize(vector<int> &initialized_clustering);
    void AllAloneClusteringInitialize(vector<int> &initialized_clustering);
    void GivenClusteringInitialize(vector<int> &initialized_clustering, const vector<int> &input_clustering);

    static unsigned int GetClusterNumber(const vector<int> &input_clustering);
    static unsigned int GetMaxClusterNo(vector<int> &input_clustering);

    static double CalculateNMIDistance(unsigned int point_number,
      vector<int> &evaluated_clustering,
      vector<int> &original_clustering);
    static double CalculateNMIDistance(unsigned int point_number,
      vector<int> &evaluated_clustering, unsigned int evaluated_clustering_cluster_number,
      vector<int> &original_clustering, unsigned int original_clustering_cluster_number);
    static double CalculateVIDistance(unsigned int point_number,
      vector<int> &evaluated_clustering,
      vector<int> &original_clustering);
    static double CalculateVIDistance(unsigned int point_number,
      vector<int> &evaluated_clustering, unsigned int evaluated_clustering_cluster_number,
      vector<int> &original_clustering, unsigned int original_clustering_cluster_number);
    static double CalculateAdjustedRandDistance(unsigned int point_number,
      vector<int> &evaluated_clustering,
      vector<int> &original_clustering);

  protected:
    void UpdateClusterNumber(const vector<int> &input_clustering);  // Used in AddOriginalClustering

    static double MI(unsigned int point_number,
      vector<unsigned int> &evaluated_clustering_cluster_size_table, unsigned int evaluated_clustering_cluster_number,
      vector<unsigned int> &original_clustering_cluster_size_table, unsigned int original_clustering_cluster_number,
      vector<vector<unsigned int>> &intersection_counter_table);
    static double H(unsigned int point_number, vector<unsigned int> &cluster_number_table, int K);
    static double LogBase(double a, double base);
    static void GetClusterSizeTable(unsigned int point_number,
      vector<int> &input_clustering, unsigned int input_clustering_cluster_number,
      vector<unsigned int> &cluster_size_table);
    static void GetIntersectionCounterTable(unsigned int point_number,
      vector<int> &evaluated_clustering, unsigned int evaluated_clustering_cluster_number,
      vector<int> &original_clustering, unsigned int original_clustering_cluster_number,
      vector<vector<unsigned int>> &intersection_counter_table);
  };

  class Fragment
  {
  public:
    vector<unsigned int> point_list;
    vector<int> belong_to_cluster_list; // this fragment is belong to which cluster of each original clustering

  public:
    Fragment(void);
    Fragment(vector<int> &input_belong_to_cluster_list);
    Fragment(const Fragment &fragment);
    ~Fragment(void);

    void operator=(const Fragment &fragment);

    void AddPoint(unsigned int point);
    vector<Fragment> SplitByClustering(const vector<int> &input_clustering);

    double GetNormalDistanceToPoint(const vector<int> &target_point_belong_to_cluster_list);
    double GetNormalDistanceToFragment(const Fragment &target_fragment);

    double GetLFCARSSimilarityToPoint(const vector<int> &target_point_belong_to_cluster_list);
    double GetLFCARSSimilarityToFragment(const Fragment &target_fragment);
    double GetTotalLFCARSSimilarityToFragment(const Fragment &target_fragment);
  };

  class LooseFragment
  {
  public:
    Fragment core_fragment;
    vector<Fragment> noncore_fragment_list;
    // 0.0 <= max_point_error <= error_threshold <= 2.0
    double error_threshold;
    double max_point_error;
    unsigned int total_size;
    unsigned int non_core_fragments_total_size;

  public:
    LooseFragment(const Fragment &core);
    ~LooseFragment(void);

    // AddFragment MUST update max_point_error, total_size and non_core_fragments_total_size
    void AddFragment(const Fragment &fragment);
    // RemoveFragment MAY update max_point_error, total_size and non_core_fragments_total_size
    void RemoveFragment(unsigned int fragment_no);

    void UpdateMaxError(void);

    double GetLFCARSSimilarityToFragment(const Fragment &target_fragment);
    double GetErrorToFragment(const Fragment &target_fragment);

    double GetLFCARSSimilarityToLooseFragment(const LooseFragment &target_loose_fragment);
    double GetTotalLFCARSSimilarityToLooseFragment(const LooseFragment &target_loose_fragment);

    double GetMaxErrorToLooseFragment(const LooseFragment &target_loose_fragment);
    double GetTotalMaxErrorToLooseFragment(const LooseFragment &target_loose_fragment);
  };

  // LFCARSv100 : FCARS with missing value processing
  // 1. Use AddOriginalClustering() to add all original clusterings
  // 2. Use Initialize() to do initializations
  // 3. Use Run() to run algorithm
  // 4. If needs, Use Reset() to reset for next run
  class LFCARSv100 : public ConsensusClusteringApproach
  {
  public:
    vector<int> loose_fragment_clustering_result;
    vector<unsigned int> point_to_fragment_table;
    vector<Fragment> fragment_list;
    vector<LooseFragment> loose_fragment_list;
    double error_threshold;
    unsigned int core_fragment_size_threshold;  // Fragment which its size greater than core_fragment_size_threshold MUST be a Core Fragment

    bool output_loose_fragment_clustering_result;
    string output_loose_fragment_clustering_result_filename;

    bool given_initialization;
    string used_initialization_approach;
    string given_initialization_filename;

    unsigned int incremental_point_number;

    bool LFCARSv100_use_avg_similarity;

    string loose_fragment_sampling_method;
    string output_loose_fragment_sampling_filename;

    bool debug_show_round;
    string debug_show_round_output_filename;
    bool debug_show_fragment_number;
    string debug_show_fragment_number_output_filename;

  public:
    LFCARSv100(const ConsensusClusteringInformation &consensus_clustering_information);
    ~LFCARSv100(void);

    // AddOriginalClustering MUST update original_clustering_number, sum_cluster_number, max_cluster_number and fragment_list
    void AddOriginalClustering(const vector<int> &input_clustering);

    // Initialize MUST build point_to_fragment_table and loost_fragment_list
    // Initialize MUST do clustering initialize
    void Initialize(void);
    // Reset MUST clear original_clustering_number, sum_cluster_number and max_cluster_number
    // Reset MUST build point_to_fragment_table and loost_fragment_list
    // Reset MUST do clustering initialize
    void Reset(void);
    void KeepResultAndReset(void);
    void Run(void);

    void ExtendSize(unsigned int new_size);
    void SampleFromLooseFragmentsToFile(void);

    static bool CompareFragmentSize(const Fragment &fragment_1, const Fragment &fragment_2);  // for sorting Fragments

  private:
    void BuildPointToFragmentTable(void);
    // BuildLooseFragmentList MUST resize loose_fragment_clustering_result
    void BuildLooseFragmentList(void);

    void LFCARSCoreInitialize(vector<int> &loose_fragment_clustering_result, const vector<int> &input_clustering);

    unsigned int FindMostSimilarCluster(unsigned int target_loose_fragment_no, double &best_similarity);
    // if loose_fragment_no == cluster_no or moving is failed, return false
    bool MoveLooseFragmentToCluster(unsigned int loose_fragment_no, unsigned int cluster_no);
    void CreateNewClusterForLooseFragment(unsigned int loose_fragment_no);

    // RepresentativePointOfEachCoreFragment
    void SampleFromLooseFragmentsMethod1(vector<unsigned int> &sampling_point_list);
    // OneRandomPointEachCoreFragment
    void SampleFromLooseFragmentsMethod2(vector<unsigned int> &sampling_point_list);

    // translate the loose_fragment_clustering_result to clustering_result, the final result
    void TranslateLooseFragmentsToClusteringResult(void);
  };

  class FAgglomerative : public ConsensusClusteringApproach
  {
  public:
    vector<int> fragment_clustering_result;
    vector<unsigned int> point_to_fragment_table;
    vector<Fragment> fragment_list;
    vector<vector<unsigned int>> cluster_to_fragments_list;

    bool debug_show_round;
    string debug_show_round_output_filename;
    bool debug_show_fragment_number;
    string debug_show_fragment_number_output_filename;

  public:
    FAgglomerative(const ConsensusClusteringInformation &consensus_clustering_information);
    ~FAgglomerative(void);

    // AddOriginalClustering MUST update original_clustering_number, sum_cluster_number, max_cluster_number and fragment_list
    void AddOriginalClustering(const vector<int> &input_clustering);

    // Initialize MUST build point_to_fragment_table
    // Initialize MUST do clustering initialize
    void Initialize(void);
    // FAgglomerative does not need Reset() because it only runs one time
    void Reset(void);
    void Run(void);

  private:
    void BuildPointToFragmentTable(void);
    double CalculateDistanceBetweenClusters(unsigned int cluster1, unsigned int cluster2);

    // translate the fragment_clustering_result to clustering_result, the final result
    void TranslateFragmentsToClusteringResult(void);

    static bool FragmentNoCompare(const Fragment &fragment1, const Fragment &fragment2);
  };

  class SimilarityMatrix
  {
  private:
    unsigned int number_of_clustering;
    vector<vector<int>> similarity_matrix;

  public:
    SimilarityMatrix(void);
    SimilarityMatrix(unsigned int n_of_vertices);
    ~SimilarityMatrix(void);

    void Initialize(unsigned int n_of_vertices);
    void Reset(void);

    // return false if the size of input_clustering and similarity_matrix are different
    bool AddOriginalClustering(const vector<int> &input_clustering);

    unsigned int GetNumberOfClustering(void);
    void TransformToCSPAFormat(idx_t *&xadj, idx_t *&adjncy, idx_t *&vwgt, idx_t *&vsize, idx_t *&adjwgt);
  };

  class CSPA : public ConsensusClusteringApproach
  {
  public:
    string cluster_number_choosing_method;
    double cluster_number_multiple;

    SimilarityMatrix similarity_matrix;

  public:
    CSPA(const ConsensusClusteringInformation &consensus_clustering_information);
    ~CSPA(void);

    // AddOriginalClustering MUST update original_clustering_number, sum_cluster_number and max_cluster_number
    void AddOriginalClustering(const vector<int> &input_clustering);

    void Initialize(void);
    // CSPA does not need Reset() because it only runs one time
    void Reset(void);
    void Run(void);
  };

  class Hypergraph
  {
  private:
    unsigned int number_of_vertices;
    unsigned int number_of_clustering;
    vector<vector<unsigned int>> hyperedges;

  public:
    Hypergraph(void);
    Hypergraph(unsigned int n_of_vertices);
    ~Hypergraph(void);

    void Initialize(unsigned int n_of_vertices);
    void Reset(void);

    // return false if the size of input_clustering and similarity_matrix are different
    bool AddOriginalClustering(const vector<int> &input_clustering);

    unsigned int GetNumberOfClustering(void);
    unsigned int GetNumberOfHyperedges(void);

    void TransformToHMETISFormat(int &nhedges, int *&eptr, int *&eind);
    void TransformToMCLAFormat(idx_t *&xadj, idx_t *&adjncy, idx_t *&vwgt, idx_t *&vsize, idx_t *&adjwgt);
    
    void CollapseHyperedges(unsigned int point_number, idx_t nvtxs, idx_t *part, vector<vector<double>> &meta_hypergraph);

    double BinaryJaccardSimilarity(unsigned int edge1, unsigned int edge2);

  private:
    double Dot(unsigned int edge1, unsigned int edge2);
  };

/*
  class HGPA : public ConsensusClusteringApproach 
  {
  public:
    string cluster_number_choosing_method;
    double cluster_number_multiple;

    unsigned int sum_cluster_number;
    Hypergraph hypergraph;

  public:
    HGPA(const ConsensusClusteringInformation &consensus_clustering_information);
    ~HGPA(void);

    // AddOriginalClustering MUST update original_clustering_number and max_cluster_number
    void AddOriginalClustering(const vector<int> &input_clustering);

    void Initialize(void);
    void Reset(void);
    void Run(void);
  };
*/

  class MCLA : public ConsensusClusteringApproach
  {
  public:
    string cluster_number_choosing_method;
    double cluster_number_multiple;

    Hypergraph hypergraph;

  public:
    MCLA(const ConsensusClusteringInformation &consensus_clustering_information);
    ~MCLA(void);

    // AddOriginalClustering MUST update original_clustering_number, sum_cluster_number and max_cluster_number
    void AddOriginalClustering(const vector<int> &input_clustering);

    void Initialize(void);
    // MCLA does not need Reset() because it only runs one time
    void Reset(void);
    void Run(void);
  };
}
